﻿/*
    Copyright 2008 Travis Scholtens
    
    This file is part of Anticipa-Do List.

    Anticipa-Do List is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Anticipa-Do List is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Anticipa-Do List.  If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;

namespace Anticipa_Do_List
{
    /// <summary>
    /// The type of an event in a periodically performed task.
    /// </summary>
    enum EventType
    {
        /// <summary>
        /// The task has been performed.
        /// </summary>
        Done,
        /// <summary>
        /// The task needs to be performed.
        /// </summary>
        NeedsDoing
    }

    /// <summary>
    /// An event in a periodically performed task.
    /// </summary>
    struct Event
    {
        public Event(DateTime time, EventType type) { Time = time; Type = type; }
        public readonly DateTime Time;
        public readonly EventType Type;
    }

    /// <summary>
    /// Class representing a periodically performed task.
    /// </summary>
    class Task
    {
        /// <summary>
        /// Construct a new task object with the given name and a new unique ID.
        /// </summary>
        /// <param name="name">Text description of the task.</param>
        public Task(string name) : this(name, Guid.NewGuid()) { }

        /// <summary>
        /// Construct a new task object with the given name unique ID.
        /// </summary>
        /// <param name="name">Text description of the task.</param>
        /// <param name="id">The task's unique ID.</param>
        public Task(string name, Guid id) { Name = name; ID = id;  }

        /// <summary>
        /// Gets the task description.
        /// </summary>
        public readonly string Name;

        /// <summary>
        /// Gets the task's unique ID.
        /// </summary>
        public readonly Guid ID;

        private List<Event> _events = new List<Event>();

        /// <summary>
        /// Number of items in the window used when calculating moving averages.
        /// </summary>
        public const int WINDOW_SIZE = 3;

        /// <summary>
        /// Add a timestamped "task done" or "task needs doing" event to the list.
        /// </summary>
        /// <param name="item">Event structure to add.</param>
        public void AddEvent(Event item) 
        {
            // insert before first existing item that comes after, or append if none
            int i = _events.IndexOf(_events.FirstOrDefault(e => e.Time > item.Time));
            _events.Insert(i >= 0 ? i : _events.Count, item);
        }

        /// <summary>
        /// Gets the read-only list of task events, ordered by date. Order is maintained by <c>AddEvent</c>.
        /// </summary>
        public ReadOnlyCollection<Event> Events { get { return _events.AsReadOnly(); } }

        /// <summary>
        /// Gets the next time the task is scheduled to be completed by the prediction algorithm. If there is not enough information
        /// to compute the scheduled time then null is returned.
        /// </summary>
        /// <remarks>
        /// The prediction algorithm takes a moving average of the <c>WINDOW_SIZE</c> most recent intervals that elapsed between
        /// an <c>EventType.Done</c> event and the subsequent <c>EventType.NeedsDoing</c> or <c>EventType.Done</c> event,
        /// and adds the resulting interval to the time of the most recent <c>EventType.Done</c> event.
        /// </remarks>
        public DateTime? NextTime
        {
            get
            {
                var spans = new List<long>(WINDOW_SIZE);
                var lastDone = DateTime.MinValue;
                for (int i = _events.Count - 1; spans.Count < WINDOW_SIZE && i >= 0; i--)
                    if (_events[i].Type == EventType.Done)
                    {
                        if(lastDone == DateTime.MinValue) lastDone = _events[i].Time;
                        if(i <= _events.Count - 2)
                            spans.Add((_events[i + 1].Time - _events[i].Time).Ticks);
                    }
                return spans.Count > 0 && lastDone != DateTime.MinValue ?
                    (DateTime?)(lastDone + new TimeSpan((long)spans.Average())) :
                    null;
            }
        }

        public override string ToString()
        {
            return Name;
        }
    }
}
